using System;
using System.Threading;

namespace GeoBox.BoxApplication.CellularRadioDriver
{
    /// <summary>
    /// A helper to provide TCP capabilities to a <see cref="CellularRadio"/>
    /// </summary>
    public class CellularRadioManager
    {
        /// <summary>
        /// The International Mobile Station Equipment Identity number of the internal <see cref="CellularRadio"/>
        /// see http://en.wikipedia.org/wiki/International_Mobile_Station_Equipment_Identity
        /// </summary>
        public string IMEI { get; private set; }
        /// <summary>
        /// Print Debug messages to Output
        /// </summary>
        public bool DebugPrintEnabled { get; set; }
        /// <summary>
        /// The GPRS Access Point Name provided by the SIM card provider
        /// </summary>
        public string AccessPointName { get; set; }
        /// <summary>
        /// The GPRS Username provided by the SIM card provider (default empty)
        /// </summary>
        public string AccessPointUser { get; set; }
        /// <summary>
        /// The GPRS Password provided by the SIM card provider (default empty)
        /// </summary>
        public string AccessPointPassword { get; set; }
        public string IpAddress { get; private set; }

        private bool _gsmIsAttached;
        private bool _gprsIsAttached;
        private bool _imeiIsRetrieved;
        private bool _cellRadioIsReady;
        private bool _gprsConnectionRequestSent;

        private readonly int _radioSocket;
        // ReSharper disable NotAccessedField.Local
        private readonly Thread _thread;
        // ReSharper restore NotAccessedField.Local
        /// <summary>
        /// The current <see cref="RadioState"/> of the internal <see cref="CellularRadio"/>
        /// </summary>
        public RadioState RadioState { get; private set; }

        private readonly CellularRadio _radio;

        /// <summary>
        /// Creates a new CellularRadioManager with a <see cref="CellularRadio"/> on the supplied socket
        /// NOTE: Must be called from the Dispatcher thread
        /// </summary>
        /// <param name="socketNumber">The Gadgeteer socket number that the modem is attached to</param>
        public CellularRadioManager(int socketNumber)
        {
            IpAddress = string.Empty;
            RadioState = RadioState.Unitialized;
            _radioSocket = socketNumber;
            _radio = new CellularRadio(_radioSocket) { DebugPrintEnabled = true };	// create new CellularRadio on the supplied Gadgeteer socket
            _radio.PowerOff();
            RadioState = RadioState.PowerOff;
            _thread = new Thread(RadioManagerThreadRun);
            _thread.Start();
        }

        #region ITcpServerConnection
        /// <summary>
        /// Radio is ready for TCP activity
        /// </summary>
        public bool IsReady { get { return RadioState == RadioState.Ready; } }
        /// <summary>
        /// Radio can be used to send data (TCP is connected to a server)
        /// </summary>
        public bool CanSendData { get { return IsReady && _isTcpConnected; } }
        private readonly TimeSpan _tcpConnectTimeout = new TimeSpan(0, 0, 10);
        private bool _isTcpConnected;

        /// <summary>
        /// Connect to a TCP server.  NOTE: only one simultaneous connection is supported
        /// </summary>
        /// <param name="serverAddress">IP Address or Hostname to connect to</param>
        /// <param name="port">The port to use for this connection</param>
        /// <returns>true if connection successful</returns>
        public bool Connect(string serverAddress, int port = 80)
        {
            if (!IsReady) return false;

            var tcpState = _radio.ConnectTCP(serverAddress, port, _tcpConnectTimeout);

            _isTcpConnected = tcpState == CellularRadio.ReturnedState.OK;
            return _isTcpConnected;
        }

        /// <summary>
        /// Disconnect from the current TCP server
        /// </summary>
        /// <returns>true if disconnetion successful</returns>
        public bool Disconnect()
        {
            if (!IsReady) return false;

            Thread.Sleep(1000);
            _radio.SendRawTcpData("+++"); // set to command mode refer manual for correct timings
            Thread.Sleep(500);
            _radio.IsInDataMode = false;

            var disconnectState = _radio.DisconnectTCP();
            _isTcpConnected = false;
            return disconnectState == CellularRadio.ReturnedState.OK;
        }

        /// <summary>
        /// Send data to a TCP server. NOTE: This is a synchronous operation
        /// </summary>
        /// <param name="data"></param>
        /// <returns>true if data was sent successfully</returns>
        public bool SendData(string data)
        {
            if (!CanSendData) return false;

            _radio.IsInDataMode = true;
            var result = _radio.SendRawTcpData(data);	// Blocking call
            return result == CellularRadio.ReturnedState.OK;
        }


        #endregion

        private void RadioManagerThreadRun()
        {
            _radio.ImeiRetrieved += ImeiRetrieved;
            _radio.GprsAttached += GprsAttached;
            _radio.GsmNetworkRegistrationChanged += GsmNetworkRegistrationChanged;

            _radio.PowerOn(30);
            RadioState = RadioState.StartingUp;

            Thread.Sleep(1000);

            while (true)
            {
                if (!_cellRadioIsReady)
                {
                    _cellRadioIsReady = _radio.RetrievePhoneActivity() == CellularRadio.ReturnedState.OK;
                    RadioState = RadioState.StartingUp;
                }

                else if (!_gsmIsAttached)
                {
                    _radio.SendATCommand("AT+CREG?");
                    RadioState = RadioState.WaitingForNetworkRegistration;
                    Thread.Sleep(5000);
                }

                else if (!_imeiIsRetrieved)
                {
                    RadioState = RadioState.WaitingForIMEI;
                    _radio.RetrieveImei();
                    Thread.Sleep(5000);
                }

                else if (!_gprsIsAttached)
                {
                    if (!_gprsConnectionRequestSent)
                    {
                        bool result;
                        result = _radio.SendATCommand("AT+CIPMODE=1") == CellularRadio.ReturnedState.OK;
                        if (!result) return;
                        Thread.Sleep(2000);
                        result = _radio.AttachGPRS(AccessPointName, AccessPointUser, AccessPointPassword) == CellularRadio.ReturnedState.OK;
                        if (!result) return;
                        Thread.Sleep(10000);
                        // check IP
                        if (IpAddress.Length < 6) return;
                        _gprsConnectionRequestSent = true;
                    }
                }
                else
                {
                    RadioState = RadioState.Ready;
                }
                Thread.Sleep(2000);
            }
        }


        private void GsmNetworkRegistrationChanged(CellularRadio sender, CellularRadio.NetworkRegistrationState networkState)
        {
            if (networkState == CellularRadio.NetworkRegistrationState.Registered ||
                networkState == CellularRadio.NetworkRegistrationState.Roaming)
            {
                DebugPrint("GSM Network Registered");

                _gsmIsAttached = true;
            }
            else
            {
                _gsmIsAttached = false;
                DebugPrint("GSM Network Registration Error");
            }
        }

        private void GprsAttached(CellularRadio sender, string ipAddress)
        {
            _gprsIsAttached = true;
            IpAddress = ipAddress;
            DebugPrint("IP: " + ipAddress);
        }

        private void ImeiRetrieved(CellularRadio sender, string imei)
        {
            IMEI = imei;
            _imeiIsRetrieved = true;
            DebugPrint("IMEI: " + IMEI);
        }

        private void DebugPrint(string debugString)
        {
            if (DebugPrintEnabled)
            {
                Debug.Print(debugString);
            }
        }
    }
}
